/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    myuvc.c
 * @brief   实现USB摄像头的数据传输（简单函数）
 * @author  K
 * @version 0.0.1
 * @date    2021-07-22
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>         | <版本>      | <作者>    | <描述>
 * 2021-07-22   | v0.0.1    | Kcode   | 实现USB摄像头的数据传输（简单函数）
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>

#define MYDRIVER_VERSION_NUMBER     1		/**< myuvc版本 */

/*!
 * 分辨率描述
 */
typedef struct frame_desc {
	int width;		/**< x分辨率 */
	int height;		/**< y分辨率 */
} FRAME_DESC_S;

/*!
 * 缓冲区的信息
 */
typedef struct myuvc_buffer {
    int state;                      /**< 状态位 */
    int vma_use_count;              /**< 是否已经被mmap */
	struct v4l2_buffer buf;         /**< 存储每个缓冲区的查询信息 */
    wait_queue_head_t wait;         /**< APP读取某个缓冲区，如果无数据，在此休眠 */
    struct list_head stream;        /**< mainqueue队列结点，供APP消费用 */
    struct list_head irq;           /**< irqqueue队列结点，供底层驱动生成用*/
}MYUVC_BUFFER_S; 

/*!
 * 存储分配的整块缓冲区
 */
typedef struct mvuvc_video_queue {
    int count;                          /**< 分配缓冲区个数 */
    int buf_size;                       /**< 每个缓冲区（页对齐）大小 */
	void *mem;                          /**< 存储分配的内存 */
	struct MYUVC_BUFFER_S buffer[32];   /**< 存储每个缓冲区的信息 */
    struct list_head mainqueue;         /**< mainqueue队列头结点，供APP消费用 */
    struct list_head irqqueue;          /**< irqqueue队列头结点，供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;

static MYUVC_VIDEO_QUEUE_S s_myuvc_queue;	/**< 存放分配的一整块缓冲区 */

static struct video_device *s_myuvc_vdev;	/**< video */
static struct v4l2_format s_myuvc_format;	/**< USB摄像头的format */
static int s_pixel_bits  = 16;				/**< USB摄像头像素位 */
static int s_frame_index = 1;				/**< 指定分辨率数组下标 */
static FRAME_DESC_S s_frame_arr[] = {
	{640, 480},
	{352, 288},
	{320, 240},
	{176, 144},
	{160, 120},
};	/**< 该USB摄像头所有支持的分辨率 */

/*!
 * 所支持usb设备类的接口
 */
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },	 /**< VideoControl interface */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */
	{}
};

/*!
 * @brief  Step1  - 打开myuvc_fops设备文件
 */
static int myuvc_open(struct file *file)
{
	return 0;
}

/*!
 * 关闭myuvc_fops设备文件
 */
static int myuvc_close(struct file *file)
{
	return 0;
}

/*!
 * @brief  Step2 - 查询是否为USB摄像头设备
 */
static int myuvc_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{	
	/*!
	 * 清空内存、设置版本号和名字
	 */
	memset(cap, 0, sizeof *cap);
	strcpy(cap->driver, "myuvc");
	strcpy(cap->card, "myuvc");
	cap->version = MYDRIVER_VERSION_NUMBER;

	/*!
	 * V4L2_CAP_VIDEO_CAPTURE - 设备为视频捕捉设备
	 * V4L2_CAP_STREAMING     - 使用ioctl来读视频数据
	 */
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;

	return 0;
}

/*!
 * @brief  Step3 - 列举USB摄像头设备所支持的格式format
 */
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_fmtdesc *f)
{
	/*!
	 * 当前USB摄像头只支持一种格式format(命令查看描述符信息可知)
	 */
	if (f->index >= 1)
		return -EINVAL;

	/*!
	 * 格式：VS_FORMAT_UNCOMPRESSED（不压缩原始数据）
	 * GUID：59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71 10
	 * 参考：uvc_fmts[]得到 最终格式宏定义V4L2_PIX_FMT_YUYV
	 */
	strcpy(f->description, "4:2:2, packed, YUYV");
	f->pixelformat = V4L2_PIX_FMT_YUYV;

	return 0;
}

/*!
 * @brief  Step4 - 返回当前所使用的格式
 */
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_format *f)
{
	memcpy(f, &s_myuvc_format, sizeof(s_myuvc_format));
	return 0;
}

/*!
 * @brief  Step5 - 测试驱动程序是否支持某种格式，强制设定格式
 */
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file,
							void *priv, struct v4l2_format *f)
{
	if ((f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || \
		(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV))
		return -EINVAL;

	/*!
	 * 手工确定分辨率、像素位、图片大小信息
	 */
	f->fmt.pix.width  = s_frame_arr[s_frame_index].width;
	f->fmt.pix.height = s_frame_arr[s_frame_index].height;

	f->fmt.pix.bytesperline = (f->fmt.pix.width * s_pixel_bits) >> 3;
	f->fmt.pix.sizeimage    = f->fmt.pix.height * f->fmt.pix.width;
	
	return 0;
}

/*!
 * @brief  Step6 - 设置所支持的格式
 */
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file,
							void *priv, struct v4l2_format *f)
{
	int ret;

	/*!
	 * 测试是否支持该格式（强制设置格式）
	 */
	ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);	
	if (ret < 0)
		return ret;
	
	memcpy(&s_myuvc_format, f, sizeof(s_myuvc_format));

	return 0;
}

/*!
 * @brief  释放分配的缓冲区
 */
int myuvc_free_buffers(void)
{
    kfree(s_myuvc_queue.mem);
    memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));
	return 0;
}

/*!
 * @brief  Step7 - 为该设备申请若干个缓冲区，分配头部信息
 * @return   正数：返回成功分配内存的大小，负数：分配失败
 */
static int myuvc_vidioc_reqbufs(struct file *file,
						void *priv, struct v4l2_requestbuffers *p)
{
	int buf_num   = p->count ;
	int buf_size_unalign = s_myuvc_format.fmt.pix.sizeimage;
	int buf_size_align    = PAGE_ALIGN(s_myuvc_format.fmt.pix.sizeimage);
	unsigned int i;
	void *mem = NULL;
	int ret;

	if (buf_num > UVC_MAX_VIDEO_BUFFERS)
		buf_num = UVC_MAX_VIDEO_BUFFERS;

	/* 释放之前分配的缓存 */
	if ((ret = uvc_free_buffers()) < 0)
		goto done;

	/* 如果不分配缓冲区，则退出 */
	if (buf_num == 0)
		goto done;

	/* 减少缓冲区的数量，直到分配成功 */
	for (; buf_num > 0; --buf_num) {
		mem = vmalloc_32(buf_num * bufsize);
		if (mem != NULL)
			break;
	}

	if (mem == NULL) {
		ret = -ENOMEM;
		goto done;
	}

	memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue))
	
	/*!
	 * 初始化mainqueue和irqqueue队列
     */
    INIT_LIST_HEAD(s_myuvc_queue.mainqueue);
    INIT_LIST_HEAD(s_myuvc_queue.irqqueue);
    
    /*!
	 * 缓存是一次性分配一个大的整体
	 * 需分别设置每个缓存的信息
	 */
	for (i = 0; i < buf_num; ++i) {
		s_myuvc_queue->buffer[i].buf.index     = i;
		s_myuvc_queue->buffer[i].buf.m.offset = i * bufsize;
		s_myuvc_queue->buffer[i].buf.length    = buf_size_unalign;
		s_myuvc_queue->buffer[i].buf.type      = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		s_myuvc_queue->buffer[i].buf.sequence = 0;
		s_myuvc_queue->buffer[i].buf.field     = V4L2_FIELD_NONE;
		s_myuvc_queue->buffer[i].buf.memory    = V4L2_MEMORY_MMAP;
		s_myuvc_queue->buffer[i].buf.flags     = 0;

        /* 空闲状态 */
		s_myuvc_queue->buffer[i].state         = VIDEOBUF_IDLE;

        /* 初始化队列 */
		init_waitqueue_head(&s_myuvc_queue->buffer[i].wait);
	}

	s_myuvc_queue->mem = mem;
	s_myuvc_queue->count = buf_num;
	s_myuvc_queue->buf_size = buf_size_align;
	ret = buf_num;

done:
	return ret;
}

/*!
 * @brief  Step8 - 查询指定缓冲区的信息，如大小、偏移地址等
 *          得到信息后，APP可mmap进行地址映射，分配真正的存储数据的缓冲区
 * @return  0：成功，负数：失败
 */
static int myuvc_vidioc_querybuf(struct file *file, 
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    int ret = 0;

    if (v4l2_buf->index >= s_myuvc_queue.count) {
        ret = -EINVAL;
        goto done;
    }

    /* 拷贝该缓冲区的状态信息 */
    memcpy(v4l2_buf, s_myuvc_queue.buffer[v4l2_buf->index].buf,
                sizeof(*v4l2_buf));

    /*!
     * 若已经该缓冲区已被mmap，则更新状态
     */
    if (s_myuvc_queue.buffer[v4l2_buf->index]->vma_use_count)
        v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;

    /*!
     * 更新状态
     */
	switch (s_myuvc_queue.buffer[v4l2_buf->index]->state) {
	case VIDEOBUF_ERROR:
	case VIDEOBUF_DONE:
		v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
		break;
	case VIDEOBUF_QUEUED:
	case VIDEOBUF_ACTIVE:
		v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
		break;
	case VIDEOBUF_IDLE:
	default:
		break;
    }

done:
	return ret;
}

/*!
 * @brief  Step9 - APPmmap进行地址映射可直接操作这块内存，分配真正的存储数据的缓冲区
 * @return 0：成功
 */
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{

}

/*!
 * @brief  Step10 - 把申请的缓冲区放入队列，底层的硬件操作函数将会把数据放入队列
 */
static int myuvc_vidioc_qbuf(struct file *file, 
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];
    
    /*!
     * 修改状态：处于队列状态，且缓冲区数据为空
     */
    buf->state = VIDEOBUF_QUEUED;
    v4l2_buf->bytesused = 0;

    /*!
     * 队列一：供APP使用
     * 当缓冲区没有数据时，放入mainqueue队列
     * 当缓冲区有数据时，APP从mainqueue队列中取出
     */
	list_add_tail(&buf->stream, &s_myuvc_queue->mainqueue);

    /*!
     * 队列二：供产生数据的函数使用
     * 当采集到数据时，从irqqueue队列中取出第一个缓冲区，存入数据
     */
	list_add_tail(&buf->irq, &s_myuvc_queue->irqqueue);

	return 0;
}

/*!
 * @brief  Step11 - 启动数据传输
 */
static int myuvc_vidioc_streamon(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
	return 0;
}

/*!
 * @brief  Step12 - APP调用poll/select确定缓存是否有数据
 */
static unsigned int myuvc_poll(struct file *file, 
									struct poll_table_struct *wait)
{
	return 0;
}


/*!
 * @brief  Step13 - APP通过poll/select确定缓冲区有数据后，从队列中取出并删除缓冲区
 */
static int myuvc_vidioc_dqbuf(struct file *file,
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];

    /* APP发现数据后，从mianqueue中取出buffer */
    list_del(&buf->stream);
    
	return 0;
}

/*!
 * @brief  Step14 - APP已经mmap映射缓存，可直接读数据
 *         Step15 - 再次调用myuvc_vidioc_dqbuf()，把缓存尾插法放入队列
 *         Step16 - 在其调用myuvc_poll()
 */

/*!
 * @brief  Step17 - 不使用时，停止摄像头数据传输
 */
static int myuvc_vidioc_streamoff(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
	return 0;
}

/*!
 * 所支持的ioclt函数
 */
static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {
	// 表示它是一个摄像头设备
	.vidioc_querycap      = myuvc_vidioc_querycap,

	/* 用于列举、获得、测试、设置摄像头的数据的格式 */
	.vidioc_enum_fmt_vid_cap  = myuvc_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = myuvc_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = myuvc_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = myuvc_vidioc_s_fmt_vid_cap,

	/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
	.vidioc_reqbufs       = myuvc_vidioc_reqbufs,
	.vidioc_querybuf      = myuvc_vidioc_querybuf,
	.vidioc_qbuf          = myuvc_vidioc_qbuf,
	.vidioc_dqbuf         = myuvc_vidioc_dqbuf,

	/* 启动/停止 */
	.vidioc_streamon      = myuvc_vidioc_streamon,
	.vidioc_streamoff     = myuvc_vidioc_streamoff,   
};

static const struct v4l2_file_operations myuvc_fops = {
	.owner			= THIS_MODULE,
    .open       	= myuvc_open,
    .release    	= myuvc_close,
    .mmap       	= myuvc_mmap,
    .unlocked_ioctl = video_ioctl2,
    .poll       	= myuvc_poll,
};

static void myuvc_release(struct video_device *vdev)
{}

static int myuvc_probe(struct usb_interface *intf,
		     const struct usb_device_id *id)
{	
	static int s_cnt = 0;
	
	printk("myuvc_probe : cnt = %d\n", s_cnt++);

	/*!
	 * myuvc_probe()调用第二次后执行
	 */
    if (s_cnt == 2) {
		/* 1、分配一个video_device结构体 */
		s_myuvc_vdev = video_device_alloc();

		/* 2、设置 */
		/* 注册过程需要用到release，必须设置 */
		s_myuvc_vdev->release     = myuvc_release;

		s_myuvc_vdev->fops        = &myuvc_fops;

		s_myuvc_vdev->ioctl_ops	  = &myuvc_ioctl_ops;

		/* 3、注册结构体 
		 * -1 - 自动分配次设备号
		 */
		video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);
	}
	
    return 0;
}
			 
static void myuvc_disconnect(struct usb_interface *intf)
{
	static int s_cnt = 0;

	printk("myuvc_disconnect : cnt = %d\n", s_cnt++);
	
	/*!
	 * myuvc_disconnect()调用第二次后执行
	 */
	if (s_cnt == 2) {
		/* 注销结构体 */
		video_unregister_device(s_myuvc_vdev);
		
		/* 释放结构体 */
		video_device_release(s_myuvc_vdev);		
	}
}

struct usb_driver myuvc_driver = {
	.name		= "myuvcvideo",
	.probe		= myuvc_probe,
	.disconnect	= myuvc_disconnect,
	.id_table	= myuvc_ids,
};

static int myuvc_init(void)
{
	int result;
	
	result = usb_register(&myuvc_driver);
	if (result == 0)
		printk("USB register error!\n");
	return result;
}

static void myuvc_cleanup(void)
{	
	usb_deregister(&myuvc_driver);
}

module_init(myuvc_init);
module_exit(myuvc_cleanup);

MODULE_LICENSE("GPL");


